﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="StyleCopStageProcess.cs" company="StyleCop for ReSharper Development Team">
//   Copyright (c) StyleCop for ReSharper Development Team. All rights reserved.
// </copyright>
// <summary>
//   Defines the StyleCopStageProcess type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace StyleCop.For.ReSharper.Core
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;

    using JetBrains.Application.Progress;
    using JetBrains.DocumentModel;
    using JetBrains.ReSharper.Daemon;
    using JetBrains.ReSharper.Psi;
    using JetBrains.ReSharper.Psi.ExtensionsAPI.Tree;

    using StyleCop.For.ReSharper.Diagnostics;
    using StyleCop.For.ReSharper.Options;

    #endregion

    /// <summary>
    /// Stage Process that execute the Microsoft StyleCop against the
    /// specified file.
    /// </summary>
    /// <remarks>
    /// This type is created and executed every time a .cs file is modified in the IDE.
    /// </remarks>
    public class StyleCopStageProcess : IDaemonStageProcess
    {
        #region Constants and Fields

        /// <summary>
        /// Defines the max performance value - this is used to reverse the settings.
        /// </summary>
        private const int MaxPerformanceValue = 9;

        /// <summary>
        /// Current Daemon Process running within the IDE.
        /// </summary>
        private readonly IDaemonProcess daemonProcess;

        /// <summary>
        /// First run flag.
        /// </summary>
        private static bool firstRunFlag = true;

        /// <summary>
        /// Used to reduce the number of calls to StyleCop to help with perfromance.
        /// </summary>
        private static Stopwatch performanceStopWatch;

        ///// <summary>
        ///// Used to count down to the next shadow file clean up. 
        ///// </summary>
#if !STYLECOP5
        private static Stopwatch scavengingStopwatch;
#endif
        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the StyleCopStageProcess class, using the specified
        /// <see cref="IDaemonProcess"/>.
        /// </summary>
        /// <param name="daemonProcess">
        /// <see cref="IDaemonProcess"/>to execute within.
        /// </param>
        public StyleCopStageProcess(IDaemonProcess daemonProcess)
        {
            StyleCopTrace.In(daemonProcess);

            this.daemonProcess = daemonProcess;
            InitialiseTimers();

            StyleCopTrace.Out();
        }

        #endregion

        #region Implemented Interfaces

        #region IDaemonStageProcess

        /// <summary>
        /// Executes the Microsoft StyleCop against the currently selected file within the IDE.
        /// </summary>
        /// <param name="commiter">
        /// The commiter.
        /// </param>
        /// <exception cref="ProcessCancelledException">
        /// <c>ProcessCancelledException.</c>.
        /// </exception>
        public void Execute(Action<DaemonStageResult> commiter)
        {
            StyleCopTrace.In();

            // inverse the performance value - to ensure that "more resources" actually evaluates to a lower number
            // whereas "less resources" actually evaluates to a higher number. If Performance is set to max, then execute as normal.
            if ((firstRunFlag || StyleCopOptions.Instance.ParsingPerformance == StyleCopStageProcess.MaxPerformanceValue) ||
                (performanceStopWatch.Elapsed > new TimeSpan(0, 0, 0, StyleCopStageProcess.MaxPerformanceValue - StyleCopOptions.Instance.ParsingPerformance)))
            {
                if (this.daemonProcess.InterruptFlag)
                {
                    return;
                }

                if (!this.FileIsValid())
                {
                    return;
                }

#if !STYLECOP5
                var fileScavenger = new StyleCopFileScavenger(scavengingStopwatch);

                var shadowFilePath = fileScavenger.CreateShadowCopy(this.daemonProcess.ProjectFile);

                if (shadowFilePath.Count == 0)
                {
                    return;
                }
#endif
                List<HighlightingInfo> violations;

                using (var styleCopRunner = new StyleCopRunnerInt())
                {
#if STYLECOP5
                    styleCopRunner.ExecuteStyleCop5(this.daemonProcess.ProjectFile, this.daemonProcess.Document);
#else
                    styleCopRunner.Execute(this.daemonProcess.ProjectFile, this.daemonProcess.Document, shadowFilePath);
#endif

                    violations = (from info in styleCopRunner.ViolationHighlights
                                      let range = info.Range
                                      let highlighting = info.Highlighting
                                      select new HighlightingInfo(range, highlighting)).ToList();
                }
                
                commiter(new DaemonStageResult(violations));

                // For performance reasons - clean up the shadow folders periodically
#if !STYLECOP5
                fileScavenger.ScavengeShadowCopies();
#endif
                ResetPerformanceStopWatch();

                firstRunFlag = false;
            }

            StyleCopTrace.Out();
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// Initialises the static timers used to regulate performance of file scavenging 
        /// and execution of StyleCop analysis.
        /// </summary>
        private static void InitialiseTimers()
        {
#if !STYLECOP5
            if (scavengingStopwatch == null)
            {
                scavengingStopwatch = Stopwatch.StartNew();
                scavengingStopwatch.Start();
            }
#endif

            if (performanceStopWatch == null)
            {
                performanceStopWatch = Stopwatch.StartNew();
                performanceStopWatch.Start();
            }
        }

        /// <summary>
        /// Resets the Performance Stopwatch.
        /// </summary>
        private static void ResetPerformanceStopWatch()
        {
            performanceStopWatch.Reset();
            performanceStopWatch.Start();
        }

        private bool FileIsValid()
        {
            var manager = PsiManager.GetInstance(this.daemonProcess.Solution);

            if (this.daemonProcess.ProjectFile == null)
            {
                return false;
            }

            if (!this.daemonProcess.ProjectFile.IsValid)
            {
                return false;
            }

            var file = manager.GetPsiFile(this.daemonProcess.ProjectFile, PsiLanguageType.GetByProjectFile(this.daemonProcess.ProjectFile));

            if (file == null)
            {
                return false;
            }

            var hasErrorElements = new RecursiveElementCollector<ErrorElement>().ProcessElement(file).GetResults().Any();
            StyleCopTrace.Info("File has error elements = {0}", hasErrorElements);

            return !hasErrorElements;
        }

        #endregion
    }
}